/******************************************************************************/
/*  Class FMEM_LAYER                                                          */
/*  PO: S. Maslyakov, rusoil.9@gmail.com                                      */
/*                                                                            */
/*  Revision:     1.2                                                         */
/*  Date:         2011/02/14 18:06:33                                         */
/******************************************************************************/


#include "data_types.h"
#include "Core\core.h"
#include "Spi\spi.h"
#include "Auto\automutex.h"
#include "TNKernel\tn.h"
#include "Fmem_layer\fmem_layer.h"


//==============================================================================
// App: Write data
//==============================================================================
uint32_t FMEM_LAYER::WriteData(const __FMEM_DATA * const _pData) {
    sint32_t mutex_err;
    uint32_t timeout;
    uint32_t countAddr;
    uint32_t countData;
    __AT25DF_DATA hwData;
    AUTOMUTEX SpiMutex(SPI::Get_spi_mutex());

    // Addr rang test
    if (TestAddrRange(_pData->addr) == FALSE_T) {
        return eMEM_ADDR_ERROR;
    };

    // Capacity test
    if ((_pData->dataLen > GetSpaceToEnd(_pData->addr)) || (_pData->dataLen == 0)) {
        return eMEM_DATA_ERROR;
    };

    // Capture mutex
    mutex_err = SpiMutex.LockBlock(TN_MSEC_TO_TICK(500));

    if (mutex_err != TERR_NO_ERR) {
        return eMEM_BUSY;
    };

    // Write cycle
    countAddr = _pData->addr;
    countData = 0;
    while (countData < _pData->dataLen) {
        // Determine data block for write
        hwData.dataLen = GetWriteDataLen(countAddr);
        hwData.dataLen = ((_pData->dataLen - countData) > hwData.dataLen)
                              ? hwData.dataLen : (_pData->dataLen - countData);

        // Prapare param for write
        hwData.addr.addr32 = ConvertExtAddrInHwAddr(countAddr);
        hwData.pBuff = (_pData->pBuff + countData);

        // Test at new sectror write
        if (TestNewSectorWrite(countAddr) == TRUE_T) {
            // Erase sector
            timeout = 0;  // 5 sec
            while (1) {
                const uint32_t err = BlockErase(hwData.addr, eAT25DF_BLOCK_4);
                bool_t flgOk;

                switch (err)
                {
                    //----------------------------------------------------------
                    case eMEM_OK:
                      flgOk = TRUE_T;
                    break;

                    //----------------------------------------------------------
                    case eMEM_BUSY:
                      flgOk = FALSE_T;
                    break;

                    //----------------------------------------------------------
                    case eMEM_NOT_AVAILABLE:
                    //----------------------------------------------------------
                    case eMEM_ADDR_ERROR:
                    //----------------------------------------------------------
                    case eMEM_DATA_ERROR:
                    //----------------------------------------------------------
                    case eMEM_WRITE_EN_ERROR:
                    //----------------------------------------------------------
                    case eMEM_INTERNAL_ERROR:
                      return err;
                };

                tn_task_sleep(TN_MSEC_TO_TICK(50));

                if (flgOk == TRUE_T) {
                    break;
                };

                if (timeout++ > 100) {
                    return eMEM_BUSY;
                };
            };
        };

        // Write data
        timeout = 0;
        while (1) {
            const uint32_t err = ProgramPage(&hwData);
            bool_t flgOk;

            switch (err)
            {
                //--------------------------------------------------------------
                case eMEM_OK:
                  flgOk = TRUE_T;
                break;

                //--------------------------------------------------------------
                case eMEM_BUSY:
                  flgOk = FALSE_T;
                break;

                //--------------------------------------------------------------
                case eMEM_NOT_AVAILABLE:
                //--------------------------------------------------------------
                case eMEM_ADDR_ERROR:
                //--------------------------------------------------------------
                case eMEM_DATA_ERROR:
                //--------------------------------------------------------------
                case eMEM_WRITE_EN_ERROR:
                //--------------------------------------------------------------
                case eMEM_INTERNAL_ERROR:
                  return err;
            };

            if (flgOk == TRUE_T) {
                break;
            };

            tn_task_sleep(TN_MSEC_TO_TICK(50));

            if (timeout++ == 100) {
                return eMEM_BUSY;
            };
        };

        // Shift counters
        countAddr += hwData.dataLen;
        countData += hwData.dataLen;
    };

    return eMEM_OK;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: Change data - single page
//==============================================================================
uint32_t FMEM_LAYER::ChangeData(const __FMEM_DATA * const _pData) {

    sint32_t mutex_err;
    uint32_t timeout;
    __AT25DF_DATA hwData;
    AUTOMUTEX SpiMutex(SPI::Get_spi_mutex());

    // Addr rang test
    if (TestAddrRange(_pData->addr) == FALSE_T) {
        return eMEM_ADDR_ERROR;
    };

    // Capacity test
    if ((_pData->dataLen > GetSpaceToEnd(_pData->addr)) || (_pData->dataLen == 0)) {
        return eMEM_DATA_ERROR;
    };

    // One page test
    if (_pData->dataLen > GetWriteDataLen(_pData->addr)) {
        return eMEM_DATA_ERROR;
    };

    // Capture mutex
    mutex_err = SpiMutex.LockBlock(TN_MSEC_TO_TICK(100));

    if (mutex_err != TERR_NO_ERR) {
        return eMEM_BUSY;
    };

    // Change data - switch 1 -> 0
    hwData.addr.addr32 = ConvertExtAddrInHwAddr(_pData->addr);
    hwData.pBuff = _pData->pBuff;
    hwData.dataLen = _pData->dataLen;

    // Write data
    timeout = 0;
    while (1) {
        const uint32_t err = ProgramPage(&hwData);

        switch (err)
        {
            //------------------------------------------------------------------
            case eMEM_OK:
              return eMEM_OK;

            //------------------------------------------------------------------
            case eMEM_BUSY:
            break;

            //------------------------------------------------------------------
            case eMEM_NOT_AVAILABLE:
            //------------------------------------------------------------------
            case eMEM_ADDR_ERROR:
            //------------------------------------------------------------------
            case eMEM_DATA_ERROR:
            //------------------------------------------------------------------
            case eMEM_WRITE_EN_ERROR:
            //------------------------------------------------------------------
            case eMEM_INTERNAL_ERROR:
              return err;
        };

        tn_task_sleep(TN_MSEC_TO_TICK(50));

        if (timeout++ == 100) {
            return eMEM_BUSY;
        };
    };
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: Read data
//==============================================================================
uint32_t FMEM_LAYER::ReadData(const __FMEM_DATA * const _pData) {

    __AT25DF_DATA hwData;
    AUTOMUTEX SpiMutex(SPI::Get_spi_mutex());
    sint32_t mutex_err;

    // Addr rang test
    if (TestAddrRange(_pData->addr) == FALSE_T) {
        return eMEM_ADDR_ERROR;
    };

    // Capacity test
    if ((_pData->dataLen > GetSpaceToEnd(_pData->addr)) || (_pData->dataLen == 0)) {
        return eMEM_DATA_ERROR;
    };

    // Capture mutex
    mutex_err = SpiMutex.Lock();

    if (mutex_err != TERR_NO_ERR) {
        return eMEM_BUSY;
    };

    // Read data
    hwData.addr.addr32 = ConvertExtAddrInHwAddr(_pData->addr);
    hwData.pBuff = _pData->pBuff;
    hwData.dataLen = _pData->dataLen;

    return ReadDataBytes(&hwData);
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: Erase memory
//==============================================================================
uint32_t FMEM_LAYER::EraseMemory() {

    AUTOMUTEX SpiMutex(SPI::Get_spi_mutex());
    __AT25DF_ADDRESS hwAddr;
    __eAt25DF_BLOCK block;
    uint32_t intAddr;
    uint32_t timeout;
    uint32_t pause;
    sint32_t mutex_err;

    const bool_t aligment64k = (m_fmemStartAddr % AT25DF_SECTOR_SIZE_64)
                                                             ? FALSE_T : TRUE_T;
    const bool_t aligment32k = (m_fmemStartAddr % AT25DF_SECTOR_SIZE_32) 
                                                             ? FALSE_T : TRUE_T;

    // Capture mutex
    mutex_err = SpiMutex.LockBlock(TN_MSEC_TO_TICK(500));

    if (mutex_err != TERR_NO_ERR) {
        return eMEM_BUSY;
    };

    intAddr = 0;
    while (TestAddrRange(intAddr) == TRUE_T) {
        // Convert virtual addr in hw-addr
        hwAddr.addr32 = ConvertExtAddrInHwAddr(intAddr);

        // Choice block size
        const uint32_t sizeMudMem = m_fmemAllocatedSize - intAddr;

        if ((sizeMudMem >= AT25DF_SECTOR_SIZE_64) && (aligment64k == TRUE_T)) {
            block = eAT25DF_BLOCk_64;
            intAddr += AT25DF_SECTOR_SIZE_64;
            pause = 400;
        }
        else if ((sizeMudMem >= AT25DF_SECTOR_SIZE_32) && (aligment32k == TRUE_T)) {
            block = eAT25DF_BLOCK_32;
            intAddr += AT25DF_SECTOR_SIZE_32;
            pause = 250;
        }
        else {
            block = eAT25DF_BLOCK_4;
            intAddr += AT25DF_SECTOR_SIZE_4;
            pause = 50;
        };

        // Erase
        timeout = 0;  // 5 sec
        while (1) {
            const uint32_t err = BlockErase(hwAddr, block);
            bool_t flgOk;

            switch (err)
            {
                //----------------------------------------------------------
                case eMEM_OK:
                  flgOk = TRUE_T;
                break;

                //----------------------------------------------------------
                case eMEM_BUSY:
                  flgOk = FALSE_T;
                break;

                //----------------------------------------------------------
                case eMEM_NOT_AVAILABLE:
                //----------------------------------------------------------
                case eMEM_ADDR_ERROR:
                //----------------------------------------------------------
                case eMEM_DATA_ERROR:
                //----------------------------------------------------------
                case eMEM_WRITE_EN_ERROR:
                //----------------------------------------------------------
                case eMEM_INTERNAL_ERROR:
                  return err;
            };

            tn_task_sleep(TN_MSEC_TO_TICK(50));

            if (flgOk == TRUE_T) {
                break;
            };

            if (timeout++ > 100) {
                return eMEM_BUSY;
            };
        };

        tn_task_sleep(TN_MSEC_TO_TICK(pause));
    };

    return eMEM_OK;
}
//==============================================================================
//==============================================================================